﻿using System;
using System.Linq;
using System.Runtime.InteropServices;
using System.Collections.Generic;
using System.Reflection;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Shell.Interop;
using VSLangProj80;
using VSLangProj;

namespace Xiety.CompileGenerator
{
	[ComVisible(true)]
	[CodeGeneratorRegistration(typeof(CustomToolCompileGenerator), "CompileGenerator", vsContextGuids.vsContextGuidVCSProject, GeneratesDesignTimeSource = true, GeneratorRegKeyName = "CompileGenerator")]
	[ProvideObject(typeof(CustomToolCompileGenerator))]
	[ClassInterface(ClassInterfaceType.None)]
	public sealed class CustomToolCompileGenerator : BaseCustomToolGenerator
	{
		private const string DefaultExtension = ".gen.cs";
		private const string CompileDomainName = "CompileGenerator";
		private const string CoreLibraryName = "mscorlib";

		protected override string Generate(string fname, string text)
		{
			this.SetWaitCursor();

			var outputPath = GetCurrentProjectOutputPath();

			using (var domain = new DomainHelper(CompileDomainName, outputPath))
			{
				return CreateOptions(fname, text, outputPath)
					.With(opts => domain.CreateInstanceFromAndUnwrap(Assembly.GetExecutingAssembly().Location, typeof(CompileInAnotherDomain).FullName)
						.As<CompileInAnotherDomain>()
						.With(gen => gen.Generate(opts))
						.If(o => o.Errors.NotNullAny(), ShowErrors, o => o.Results));
			}
		}

		private string ShowErrors(CompileOutput output)
		{
			output.Errors.ForEach(er => GeneratorErrorCallback(false, 1, er.ErrorText, er.Line, er.Column));
			ErrorList.Try(ShowErrorWindow).Catch(null);
			return null;
		}

		private static void ShowErrorWindow(IVsErrorList errorList)
		{
			errorList.BringToFront();
			errorList.ForceShowErrors();
		}

		private CompileOptions CreateOptions(string fname, string text, string outputPath)
		{
			var opts = new CompileOptions
			{
				GeneratorTypeName = this.FileNamespace,
				FileName = fname,
				Code = text,
				OutputPath = outputPath,
				References = CollectReferences().ToList(),
			};

			return opts;
		}

		private IEnumerable<string> CollectReferences()
		{
			return GetVSProject()
				.With(vsp => vsp.References).Cast<Reference>()
				.Select(r => r.Path)
				.Where(p => !p.Contains(CoreLibraryName));
		}

		public override string GetDefaultExtension()
		{
			return DefaultExtension;
		}
	}

	[Serializable]
	public class CompileOptions
	{
		public string Code { get; set; }
		public string FileName { get; set; }
		public string GeneratorTypeName { get; set; }
		public string OutputPath { get; set; }
		public List<string> References { get; set; }
	}
}

